package com.ruoyi.service.impl;

import com.ruoyi.domain.Document;
import com.ruoyi.domain.MFile;
import com.ruoyi.domain.SpaceDocument;
import com.ruoyi.dto.RepFile;
import com.ruoyi.mapper.MFileMapper;
import com.ruoyi.platform.constant.CommonConstant;
import com.ruoyi.platform.service.impl.BaseServiceImpl;
import com.ruoyi.platform.utils.spring.SpringUtils;
import com.ruoyi.platform.utils.uuid.IdUtils;
import com.ruoyi.service.IDocumentService;
import com.ruoyi.service.IMFileService;
import com.ruoyi.service.ISpaceDocumentService;
import com.ruoyi.utils.BaseFileUtils;
import io.minio.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.namespace.QName;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;

/**
 * 文件存储表Service业务层处理
 *
 * @author ruoyi
 * @date 2022-09-26
 */
@Service
@Slf4j
public class MFileServiceImpl extends BaseServiceImpl<MFileMapper, MFile> implements IMFileService {

    @Autowired
    private MFileMapper mFileMapper;

    @Autowired
    private MinioClient minioClient;

    @Value("${minio.bucket-name:'megmeet-bucket'}")
    private String bucket;

    @Value("${minio.url}")
    private String url;

    @Autowired
    private IDocumentService documentService;

    @Autowired
    private ISpaceDocumentService spaceDocumentService;

    /**
     * 查询文件存储表
     *
     * @param id 文件存储表主键
     * @return 文件存储表
     */
    @Override
    public MFile selectMFileById(Long id) {
        return mFileMapper.selectMFileById(id);

    }

    /**
     * 查询文件存储表列表
     *
     * @param mFile 文件存储表
     * @return 文件存储表
     */
    @Override
    public List<MFile> selectMFileList(MFile mFile) {
        return mFileMapper.selectMFileList(mFile);
    }

    /**
     * 新增文件存储表
     *
     * @param mFile 文件存储表
     * @return 结果
     */
    @Transactional
    @Override
    public int insertMFile(MFile mFile) {
        return mFileMapper.insert(mFile);
    }

    /**
     * 修改文件存储表
     *
     * @param mFile 文件存储表
     * @return 结果
     */
    @Transactional
    @Override
    public int updateMFile(MFile mFile) {
        return mFileMapper.updateById(mFile);
    }

    /**
     * 删除文件服务
     *
     * @param id
     * @return
     */
    @Override
    @Transactional
    public int removeFileById(Long id) {
        try {
            MFile mFile = get(id);
            Assert.notNull(mFile, "当前文件不存在");
            String realFileUrl = SpringUtils.getBean(BaseFileUtils.class).getFileUrlById(id.toString());
            Assert.hasText(realFileUrl, "文件路径不存在");
            //1.根据文件路径删除服务器上的文件
            logger.info("文件：{} 已删除", mFile.getFileName());
            minioClient.removeObject(RemoveObjectArgs.builder()
                    .bucket(bucket) // 存储桶
                    .object(realFileUrl) // 文件名
                    .build());
            //2.删除文件数据
            mFile.deleteById();
        } catch (Exception e) {
            logger.error("删除ID:{}文件失败", id);
        }
        return 1;
    }

    /**
     * 通过文件名称下载文件
     *
     * @param res
     * @param req
     * @param fileId
     */
    public void downloadFile(HttpServletResponse res, HttpServletRequest req, Long fileId) {
        MFile file = getById(fileId);
        Assert.notNull(file, "当前文件不存在!!");
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucket)
                .object(file.getFileName()).build();
        try (GetObjectResponse response = minioClient.getObject(objectArgs)) {
            byte[] buf = new byte[1024];
            int len;
            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
                while ((len = response.read(buf)) != -1) {
                    os.write(buf, 0, len);
                }
                os.flush();
                byte[] bytes = os.toByteArray();
                //将下载的文件经过加密处理返回新的一个byte[]
                bytes = getBytesByBaseMethods(bytes, CommonConstant.ENCRYPTION_FILE_METHOD_NAME);
                res.setCharacterEncoding("utf-8");
                res.setContentType("application/force-download");// 设置强制下载不打开
                res.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
                // filenameEncoding 方法兼容不同浏览器编码
                res.addHeader("Content-Disposition", "attachment;fileName=" + filenameEncoding(file.getOriginName(), req));
                try (ServletOutputStream stream = res.getOutputStream()) {
                    stream.write(bytes);
                    stream.flush();
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }

    @Override
    public void downloadFileByDocId(HttpServletResponse res, HttpServletRequest req, Long docId) {
        Document document = documentService.lambdaQuery().eq(Document::getId, docId).one();
        this.downloadFile(res, req, document.getFileId());
    }

    @Override
    public void downloadFileBySpaceDocId(HttpServletResponse res, HttpServletRequest req, Long spaceDocId) {
        SpaceDocument spaceDocument = spaceDocumentService.getById(spaceDocId);
        this.downloadFile(res, req, spaceDocument.getFileId());
    }


    /**
     * 设置不同浏览器编码
     *
     * @param filename 文件名称
     * @param request  请求对象
     */
    public static String filenameEncoding(String filename, HttpServletRequest request) throws UnsupportedEncodingException {
        // 获得请求头中的User-Agent
        String agent = request.getHeader("User-Agent");
        // 根据不同的客户端进行不同的编码
        filename = URLEncoder.encode(filename, "utf-8");
        return filename;
    }

    /**
     * 批量删除文件存储表
     *
     * @param ids 需要删除的文件存储表主键
     * @return 结果
     */
    @Transactional
    @Override
    public int deleteMFileByIds(Long[] ids) {
        int i = mFileMapper.deleteMFileByIds(ids);
        return i;
    }

    /**
     * 删除文件存储表信息
     *
     * @param id 文件存储表主键
     * @return 结果
     */
    @Transactional
    @Override
    public int deleteMFileById(Long id) {
        return mFileMapper.deleteMFileById(id);
    }

    @Override
    @Transactional
    public RepFile uploadFile(MultipartFile file, String type) {
        RepFile repFile = new RepFile();
        try {
            //在文件上传之前,先调用解密接口进行解密操作
//            file = decryptionFile(file);
            switch (type) {
                //图片
                case "1":
                    repFile = buildByImage(file);
                    break;
                //文档
                case "2":
                    repFile = buildByDocument(file);
                    break;
            }
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        return repFile;
    }


    public RepFile buildByImage(MultipartFile file) {
        RepFile repFile = new RepFile();
        try {
            String fileName = IdUtils.fastSimpleUUID();// 图片文件名,使用UUID随机
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucket) // 存储桶
                    .object(fileName) // 文件名
                    .stream(file.getInputStream(), file.getSize(), -1) // 文件内容
                    .contentType(file.getContentType()) // 文件类型
                    .build());
            String fileUrl = String.format("%s/%s/%s", url, bucket, fileName);
            log.info("上传文件，原文件名：{}，新文件名：{}", file.getOriginalFilename(), fileName);
            log.info("文件路径: " + fileUrl);
            MFile mFile = new MFile();
            mFile.setFileName(fileName);
            mFile.setOriginName(file.getOriginalFilename());
            mFile.setType("1");
            mFile.setFileUrl(fileUrl);
            mFile.insert();
            repFile.setOriginFileName(file.getOriginalFilename());
            repFile.setFileId(mFile.getId());
            repFile.setFileUrl(fileUrl);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        return repFile;
    }

    public RepFile buildByDocument(MultipartFile file) {
        RepFile repFile = new RepFile();
        try {
            String fileName = file.getOriginalFilename(); // 文件名
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucket) // 存储桶
                    .object(fileName) // 文件名
                    .stream(file.getInputStream(), file.getSize(), -1) // 文件内容
                    .contentType(file.getContentType()) // 文件类型
                    .build());
            String fileUrl = String.format("%s/%s/%s", url, bucket, fileName);
            log.info("上传文件，原文件名：{}，新文件名：{}", file.getOriginalFilename(), fileName);
            log.info("文件路径: " + fileUrl);
            MFile mFile = new MFile();
            mFile.setFileName(fileName);
            mFile.setOriginName(file.getOriginalFilename());
            mFile.setType("2");
            mFile.setFileUrl(fileUrl);
            mFile.insert();
            repFile.setOriginFileName(file.getOriginalFilename());
            repFile.setFileId(mFile.getId());
            repFile.setFileUrl(fileUrl);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        return repFile;
    }

    /**
     * 在文件上传之前,先调用解密接口进行解密操作
     *
     * @param multipartFile
     * @return
     */
    private MultipartFile decryptionFile(MultipartFile multipartFile) {
        try {
            //1.MultipartFile转化为byte数组
            byte[] bytes = multipartFile.getBytes();
            //2.调用解密接口 返回解密后的bytes
            byte[] bytesFromDecryptionFile = getBytesByBaseMethods(bytes, CommonConstant.DECRYPTION_FILE_METHOD_NAME);
            //3.byte[]转成MultipartFile
            MultipartFile file = new MockMultipartFile(multipartFile.getOriginalFilename(), multipartFile.getOriginalFilename(), "application/octet-stream", bytesFromDecryptionFile);
            return file;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 通用加密解密API返回新的byte方法
     *
     * @param bytes
     * @param methodName
     * @return
     */
    private byte[] getBytesByBaseMethods(byte[] bytes, String methodName) {
        try {
            // 创建动态客户端
            JaxWsDynamicClientFactory clientFactory = JaxWsDynamicClientFactory.newInstance();
            //创建client
            Client client = clientFactory.createClient(CommonConstant.PUBLIC_SECURITY_URL);
            HTTPConduit conduit = (HTTPConduit) client.getConduit();
            HTTPClientPolicy policy = new HTTPClientPolicy();
            policy.setAllowChunking(false);
            // 连接服务器超时时间 10秒
            policy.setConnectionTimeout(10000);
            // 等待服务器响应超时时间 20秒
            policy.setReceiveTimeout(20000);
            conduit.setClient(policy);
            QName qName = new QName(CommonConstant.FILE_NAMESPACE_URL, methodName);
            Object[] objects = client.invoke(qName, bytes);
            return (byte[]) objects[0];
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}
